<?php

declare(strict_types=1);

namespace App\Service;

/**
 * 文章服务类
 * 作者 薛原+刘传望 其中文章增删改查功能薛原完成，刘哥完成点赞，收藏等对文章操作功能
 * 时间：2025/10/17
 */
use App\Exception\BusinessException;
use App\Model\Articles;
use App\Model\ArticleLike;
use App\Repository\ArticleRepository;
use App\Repository\TagRepository;
use App\Repository\UserRepository;
use App\Repository\ArticleTagRepository;
use App\Repository\FavoriteFolderRepository;
use App\Repository\ArticleFavoriteRepository;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Hyperf\DbConnection\Db;
use Psr\EventDispatcher\EventDispatcherInterface;

class ArticleService
{
    #[Inject]
    private ArticleRepository $articleRepository;

    #[Inject]
    private UserRepository $userRepository;

    #[Inject]
    private ArticleTagRepository $articleTagRepository;

    #[Inject]
    private TagRepository $tagRepository;

    #[Inject]
    private Redis $redis;

    #[Inject]
    private FavoriteFolderRepository $favoriteFolderRepository;

    #[Inject]
    private ArticleFavoriteRepository $articleFavoriteRepository;

    #[Inject]
    private EventDispatcherInterface $eventDispatcher;

    private const REDIS_LIKE_SET_PREFIX = 'article:like:set:'; // 文章点赞用户集合
    private const REDIS_LIKE_COUNT_PREFIX = 'article:like:count:'; // 文章点赞数缓存

    public function getArticleById($article_id,$user_id=0)
    {
        $row = $this->articleRepository->getRowById($article_id, true);
	   if(!$row){
            return null;
        }
        
        $row['is_like'] = $this->isLike((int) $article_id,(int) $user_id);
    
        $row = [$row];
        $list_data = $this->articleTagRepository->mergeArticleTagData($row);
        $list_data = $this->userRepository->mergeUserData($list_data);
        return $list_data[0];
    }
    /**
     * 获取全部文章列表
     * @param $userId
     * @param int $size
     * @param int $page
     * @return array
     */
    public function getAllArticleList($size = 20, $page = 1 )
    {
        $data = ["size"=>$size,"page"=>$page];
        $validator_result = \App\Lib\BaseValidator::make($data, [
            'size' => 'int|min:0|max:1000',
            'page' => 'int|min:0',
        ],[
            'size.int' => '步长需要整数',
            'page.int' => '页码需要整数',
            'size.min'=>'步长有误',
            'title.max'=>'禁止一次取超过1000条数据',
            'page.min'=>'页码参数有误',
        ]);
        $list = $this->articleRepository->getList($size, $page);
        //合并user属性
        $list['data'] = $this->userRepository->mergeUserData($list['data']);
        //合并tag属性
        $list['data'] = $this->articleTagRepository->mergeArticleTagData($list['data']);
        //上述逻辑也可改为关联模型方式，但是会出现join语句
        $ret['articles'] = $list['data'];
        $ret['pagination']['page'] = $page;
        $ret['pagination']['size'] = $size;
        $ret['pagination']['total'] = $list['total'];
        $ret['pagination']['pages'] = $list['last_page'];
        return $ret;
    }
    /**
     * 获取自身的文章列表
     * @param $userId
     * @param int $size
     * @param int $page
     * @return array
     */
    public function getArticleList($userId, $size = 20, $page = 1 )
    {
        $data = ["size"=>$size,"page"=>$page];
        $validator_result = \App\Lib\BaseValidator::make($data, [
            'size' => 'int|min:0|max:1000',
            'page' => 'int|min:0',
        ],[
            'size.int' => '步长需要整数',
            'page.int' => '页码需要整数',
            'size.min'=>'步长有误',
            'title.max'=>'禁止一次取超过1000条数据',
            'page.min'=>'页码参数有误',
        ]);
        $list = $this->articleRepository->getListByUserId($userId, $size, $page);
        //合并user属性
        $list['data'] = $this->userRepository->mergeUserData($list['data']);
        //合并tag属性
        $list['data'] = $this->articleTagRepository->mergeArticleTagData($list['data']);
        //上述逻辑也可改为关联模型方式，但是会出现join语句
        $ret['articles'] = $list['data'];
        $ret['pagination']['page'] = $page;
        $ret['pagination']['size'] = $size;
        $ret['pagination']['total'] = $list['total'];
        $ret['pagination']['pages'] = $list['last_page'];
        return $ret;
    }
    /**
     * 创建文章
     */
    public function createArticle(array $data, string $userId): int
    {
        $validator_result = \App\Lib\BaseValidator::make($data, [
            'title' => 'required|string|min:2|max:255',
            'content' => 'required',
            'cover_image' => 'url',
            'content_type'=>'in:markdown,html'

        ],[
            'title.required'=>'标题不能为空！',
            'title.min'=>'标题至少两个字符',
            'title.max'=>'标题不能超过255字符',
            'content.required'=>'内容不能为空',
            'content_type.enum' =>"类型有误"
        ]);
        if (!$validator_result) {
            throw new BusinessException(\App\Lib\BaseValidator::$error,0 );
        }
        $article_id = $this->articleRepository->create($data, $userId);
        if($data['tags']){
            //检测标签逻辑
            $this->checkTags($data['tags']);
            $tags_list = $this->tagRepository->getDataByNames($data['tags']);
            $this->articleTagRepository->addRelation($article_id,$tags_list);
        }
        return $article_id;
    }

    /**
     * 文章更新
     * @param $article_id
     * @param array $data
     * @param $user_id
     * @return mixed
     * @throws \App\Lib\ValidateException
     */
    public function updateArticle($article_id, array $data, $user_id)
    {
        $article = $this->articleRepository->getRowById($article_id);
        if (!$article) {
            throw new BusinessException('文章不存在');
        }
        if ($article['user_id'] != $user_id) {
            throw new BusinessException('文章无权操作');
        }
        if($article['status'] != "draft"){
            throw new BusinessException('文章只有草稿状态才能编辑');
        }
        $validator_result = \App\Lib\BaseValidator::make($data, [
            'title' => 'required|string|min:2|max:255',
            'content' => 'required',
            'cover_image' => 'url',

        ],[
            'title.required'=>'标题不能为空！',
            'title.min'=>'标题至少两个字符',
            'title.max'=>'标题不能超过255字符',
            'content.required'=>'内容不能为空',
        ]);
        /**
         * 这块逻辑有待改进，目前做法是先把文章引用的标签删了，再根据新标签完成创建
         * 这块逻辑会重复操作article_tag表和tags表，增加了数据库操作次数，这里后续有待优化，只做变动更新
         */
        Db::beginTransaction();
        $tag_ids = $this->articleTagRepository->getTagsIdsByArticleId($article_id);
        $this->articleRepository->update($data, $article_id);
        //之前引用的标签记数-1
        $this->tagRepository->decTagsCountByIds($tag_ids);
        $this->articleTagRepository->removeRelation($article_id,$tag_ids);
        if($data['tags']){
            $this->checkTags($data['tags']);
            $tags_list = $this->tagRepository->getDataByNames($data['tags']);
            $this->articleTagRepository->addRelation($article_id,$tags_list);
        }
        Db::commit();
        return $article_id;
    }
    public function submitArticle($article_id, $user_id)
    {
        $article = $this->articleRepository->getRowById($article_id);
        if (!$article) {
            throw new BusinessException('文章不存在');
        }
        if ($article['user_id'] != $user_id) {
            throw new BusinessException('文章无权操作');
        }
        if($article['status'] != "draft"){
            throw new BusinessException('文章只有草稿状态才能提审');
        }
        Db::beginTransaction();
        $this->articleRepository->submit($article_id);
        Db::commit();
    }

    public function refuseArticle($article_id)
    {
        $ret = $this->getArticleById($article_id);
        if($ret['status'] != "review"){
            throw new BusinessException('只有待审文章才能审核！');
        }
        $this->articleRepository->changeStatus($article_id,"draft");
    }
    public function pulishArticle($article_id)
    {
        $ret = $this->getArticleById($article_id);
        if($ret['status'] != "review"){
            throw new BusinessException('只有待审文章才能审核！');
        }
        $res = $this->articleRepository->changeStatus($article_id,"published");
        $ret['status'] = "published";
        $ret['published_at'] = $res['published_at'];
        //发送审核成功事件
        $this->eventDispatcher->dispatch(new \App\Event\ArticlePulished($ret));

    }
    /**
     * 删除文章
     * @param $article_id
     * @param $user_id
     */
    public function deleteArticle($article_id, $user_id)
    {
        Db::beginTransaction();
        //做一层是否是自己的文章以及是否存在校验
        $article = $this->getArticleById($article_id);
        if(!$article || $article['user_id'] != $user_id){
            Db::rollBack();
            throw new BusinessException('文章不存在或无权操作');
        }
        $this->articleRepository->deleteById($article_id);
        //如果文章有标签，把标签引用次数-1
        if(count($article['tags']) > 0){
            $tags_ids = [];
            foreach($article['tags'] as $info){
                $tags_ids[] = $info['id'];
            }
            //删除标签引用计数
            $ret = $this->tagRepository->decTagsCountByIds($tags_ids);
            //删除文章-标签引用关系
            $this->articleTagRepository->removeRelation($article_id, $tags_ids);
            if(!$ret){
                Db::rollBack();
                throw new BusinessException('标签引用关系扣除计数异常');
            }
        }
        Db::commit();
    }
    /**
     * 切换文章点赞状态（点赞/取消点赞），使用 Redis + MySQL
     * @param int $articleId
     * @param int $userId
     * @return array{liked:bool,like_count:int}
     */
    public function toggleLike(int $articleId, int $userId): array
    {
        // 校验文章存在
        $row = $this->articleRepository->getRowById($articleId);
        if (!$row) {
            throw new BusinessException('文章不存在');
        }

        $setKey = self::REDIS_LIKE_SET_PREFIX . $articleId;
        $countKey = self::REDIS_LIKE_COUNT_PREFIX . $articleId;

        // 使用 Lua 脚本在 Redis 中原子切换点赞状态
        $lua = <<<'LUA'
local setKey = KEYS[1]
local countKey = KEYS[2]
local userId = ARGV[1]
local exists = redis.call('SISMEMBER', setKey, userId)
local liked = 0
if exists == 1 then
  redis.call('SREM', setKey, userId)
  liked = 0
else
  redis.call('SADD', setKey, userId)
  liked = 1
end
local count = redis.call('SCARD', setKey)
redis.call('SET', countKey, count)
return {liked, count}
LUA;

        $result = $this->redis->eval($lua, [$setKey, $countKey, (string)$userId], 2);
        $liked = ((int)($result[0] ?? 0)) === 1;
        $likeCount = (int)($result[1] ?? 0);

        // 将变更异步或即时持久化到 MySQL（这里采用幂等写入 + 计数同步）
        // 为简化，这里采用事务 + 条件写入，避免重复点赞
        Db::transaction(function () use ($liked, $articleId, $userId, $likeCount) {
            if ($liked) {
                // 插入点赞记录（若已存在则忽略）
                $exists = ArticleLike::query()->where('user_id', $userId)->where('article_id', $articleId)->exists();
                if (!$exists) {
                    ArticleLike::query()->insert([
                        'user_id' => $userId,
                        'article_id' => $articleId,
                        'created_at' => date('Y-m-d H:i:s'),
                    ]);
                }
            } else {
                // 取消点赞
                ArticleLike::query()->where('user_id', $userId)->where('article_id', $articleId)->delete();
            }
            // 同步文章点赞计数
            Articles::query()->where('id', $articleId)->update(['like_count' => $likeCount]);
        });

        return [
            'liked' => $liked,
            'like_count' => $likeCount,
        ];
    }

    /**
     * 获取文章是否已经点赞，使用 Redis即可
     * @param int $articleId
     * @param int $userId
     * @return array{liked:bool,like_count:int}
     */
    public function isLike(int $articleId, int $userId): bool
    {
        // 校验文章存在
        $row = $this->articleRepository->getRowById($articleId);
        if (!$row) {
            return false;
        }

        $setKey = self::REDIS_LIKE_SET_PREFIX . $articleId;

        return $this->redis->sismember($setKey,$userId);
    }

    /**
     * 获取文章点赞用户列表
     * @param int $articleId
     * @param int $size
     * @param int $page
     * @return array
     */
    public function getArticleLikeUsers(int $articleId, int $size = 20, int $page = 1): array
    {
        if ($size <= 0) { $size = 20; }
        if ($page <= 0) { $page = 1; }

        // 优先从 Redis Set 读取全部用户ID，再做分页（适合中小规模）
        $setKey = self::REDIS_LIKE_SET_PREFIX . $articleId;
        $userIds = $this->redis->sMembers($setKey) ?: [];

        // 如果缓存为空，从 MySQL 回填并回写 Redis
        if (empty($userIds)) {
            $rows = ArticleLike::query()
                ->where('article_id', $articleId)
                ->orderByDesc('id')
                ->pluck('user_id')
                ->toArray();
            $userIds = array_map('strval', $rows);
            if (!empty($userIds)) {
                // 写回缓存
                $this->redis->sAdd($setKey, ...$userIds);
                $this->redis->set(self::REDIS_LIKE_COUNT_PREFIX . $articleId, count($userIds));
            }
        }

        $total = count($userIds);
        $pages = (int) ceil($total / $size);
        $offset = ($page - 1) * $size;
        $pageIds = array_slice($userIds, $offset, $size);

        // 合并用户信息
        $list = [];
        if (!empty($pageIds)) {
            $uidInts = array_map('intval', $pageIds);
            $users = $this->userRepository->mergeUserData(array_map(function($uid){
                return ['user_id' => (int)$uid];
            }, $uidInts));
            // mergeUserData 返回结构中每项包含 user 字段
            foreach ($users as $item) {
                $list[] = [
                    'user_id' => $item['user_id'],
                    'user' => $item['user'],
                ];
            }
        }

        return [
            'users' => $list,
            'pagination' => [
                'page' => $page,
                'size' => $size,
                'total' => $total,
                'pages' => $pages,
            ],
        ];
    }

    private function generateSlug(string $title): string
    {
        $slug = Str::slug($title);
        $count = Article::where('slug', 'like', "{$slug}%")->count();

        return $count > 0 ? "{$slug}-{$count}" : $slug;
    }

    /**
     * 补全tag数据
     * 用于文章新增和更新用
     * @param $tags
     */
    public function checkTags($tags): void
    {
        //补全缺失tags
        $this->tagRepository->checkAndCreateTags($tags);
        //标签引用数量+1
        $this->tagRepository->incTagsCount($tags);
    }

    /**
     * 切换文章收藏状态（收藏/取消收藏）
     * @param int $articleId
     * @param int $userId
     * @param int|null $folderId
     * @return array{favorited:bool,favorite_count:int}
     */
    public function toggleFavorite(int $articleId, int $userId, ?int $folderId = null): array
    {
        // 校验文章存在
        $article = Articles::query()->where('id', $articleId)->first();
        if (!$article) {
            throw new BusinessException('文章不存在');
        }

        // 如果没有指定收藏夹，使用默认收藏夹
        if (!$folderId) {
            $folder = $this->favoriteFolderRepository->findByName($userId, '默认收藏夹');
            
            if (!$folder) {
                // 创建默认收藏夹
                $folder = $this->favoriteFolderRepository->create([
                    'user_id' => $userId,
                    'name' => '默认收藏夹',
                    'is_public' => false
                ]);
            }
            $folderId = $folder->id;
        } else {
            // 校验收藏夹是否属于当前用户
            $folder = $this->favoriteFolderRepository->findByIdAndUser($folderId, $userId);
            if (!$folder) {
                throw new BusinessException('收藏夹不存在或无权操作');
            }
        }

        // 检查是否已收藏
        $existingFavorite = $this->articleFavoriteRepository->findExistingFavorite($userId, $articleId, $folderId);

        $favorited = false;
        if ($existingFavorite) {
            // 取消收藏
            $this->articleFavoriteRepository->delete($existingFavorite);
        } else {
            // 添加收藏
            $this->articleFavoriteRepository->create([
                'user_id' => $userId,
                'article_id' => $articleId,
                'folder_id' => $folderId
            ]);
            $favorited = true;
        }

        // 更新文章收藏数
        $favoriteCount = $this->articleFavoriteRepository->getArticleFavoriteCount($articleId);
        Articles::query()->where('id', $articleId)->update(['favorite_count' => $favoriteCount]);

        return [
            'favorited' => $favorited,
            'favorite_count' => $favoriteCount,
        ];
    }

    /**
     * 获取用户收藏夹列表
     * @param int $userId
     * @return array
     */
    public function getUserFolders(int $userId): array
    {
        return $this->favoriteFolderRepository->getUserFolders($userId);
    }

    /**
     * 创建收藏夹
     * @param int $userId
     * @param string $name
     * @param bool $isPublic
     * @return array
     */
    public function createFolder(int $userId, string $name, bool $isPublic = false): array
    {
        // 检查名称是否重复
        if ($this->favoriteFolderRepository->isNameExists($userId, $name)) {
            throw new BusinessException('收藏夹名称已存在');
        }

        $folder = $this->favoriteFolderRepository->create([
            'user_id' => $userId,
            'name' => $name,
            'is_public' => $isPublic
        ]);

        return $folder->toArray();
    }

    /**
     * 更新收藏夹
     * @param int $folderId
     * @param int $userId
     * @param array $data
     * @return array
     */
    public function updateFolder(int $folderId, int $userId, array $data): array
    {
        $folder = $this->favoriteFolderRepository->findByIdAndUser($folderId, $userId);

        if (!$folder) {
            throw new BusinessException('收藏夹不存在或无权操作');
        }

        // 如果更新名称，检查是否重复
        if (isset($data['name']) && $data['name'] !== $folder->name) {
            if ($this->favoriteFolderRepository->isNameExists($userId, $data['name'], $folderId)) {
                throw new BusinessException('收藏夹名称已存在');
            }
        }

        $updatedFolder = $this->favoriteFolderRepository->update($folder, $data);
        return $updatedFolder->toArray();
    }

    /**
     * 删除收藏夹
     * @param int $folderId
     * @param int $userId
     * @return bool
     */
    public function deleteFolder(int $folderId, int $userId): bool
    {
        $folder = $this->favoriteFolderRepository->findByIdAndUser($folderId, $userId);

        if (!$folder) {
            throw new BusinessException('收藏夹不存在或无权操作');
        }

        // 删除收藏夹下的所有收藏
        $this->articleFavoriteRepository->deleteByFolderId($folderId);

        return $this->favoriteFolderRepository->delete($folder);
    }

    /**
     * 获取收藏夹中的文章列表
     * @param int $folderId
     * @param int $userId
     * @param int $size
     * @param int $page
     * @return array
     */
    public function getFolderArticles(int $folderId, int $userId, int $size = 20, int $page = 1): array
    {
        // 校验收藏夹权限
        $folder = $this->favoriteFolderRepository->findAccessibleFolder($folderId, $userId);

        if (!$folder) {
            throw new BusinessException('收藏夹不存在或无权访问');
        }

        return $this->articleFavoriteRepository->getFolderArticles($folderId, $size, $page);
    }

    /**
     * 导出收藏列表
     * @param int $userId
     * @param int|null $folderId
     * @return array
     */
    public function exportFavorites(int $userId, ?int $folderId = null): array
    {
        return $this->articleFavoriteRepository->getUserFavorites($userId, $folderId);
    }
}